/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import static org.mockfu.Exceptions.*;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * A collection of methods designed to be imported statically when using Mock Fu.
 */
public abstract class Mocks
{

    private Mocks()
    {
    }

    /**
     * Creates and registers a method parameter constraint using the class of the parameter type to
     * derive the registration key. The key which is returned is an instance of the class, or an
     * instance of a generated subtype (if it's uncertain an instance can't be created without
     * side-effect), or <tt>null</tt> if a subtype can't be generated. The user of the constraint
     * must pass the key as a parameter to an expect call or another constraint to ensure the
     * constraint forest is properly constructed.
     * <p>
     * For example, the following method registers a constraint that matches if the
     * <tt>toString()</tt> of the argument contains the specified substring:
     *
     * @param <T>
     * @param klass
     * @param matcher
     * @return
     */
    public static <T> T registerConstraint( Class<T> klass, Matcher<?> matcher )
    {
        return Constraints.registerConstraint( klass, matcher );
    }

    /**
     * Creates and registers a method parameter constraint using an instance of the parameter type
     * to derive the registration key. The key which is returned is an instance of the parameter
     * type, or an instance of a generated subtype (if it's uncertain an instance can't be created
     * without side-effect), or <tt>null</tt> if a subtype can't be generated or the argument is
     * null. The user of the constraint must pass the key as a parameter to an expect call or
     * another constraint to ensure the constraint forest is properly constructed.
     * <p>
     * For example, the following method registers a constraint that matches if the
     * <tt>toString()</tt> of the argument contains the specified substring:
     *
     * <pre>
     * public static &lt;T extends CharSequence&gt; T substring( final T value )
     * {
     *     return registerConstraint( value, new Matcher&lt;T&gt;( &quot;substring&quot;, value ) {
     *         public boolean matches( T actual )
     *         {
     *             return actual.toString().contains( value );
     *         }
     *
     *         public String description()
     *         {
     *             return &quot;contains substring \&quot;&quot; + value + &quot;\&quot;&quot;;
     *         }
     *     } );
     * }
     * </pre>
     *
     * @param <T> the method parameter type
     * @param argument an instance of method parameter type
     * @param matcher a matcher for determining when a
     * @return the constraint key
     */
    public static <T> T registerConstraint( T argument, Matcher<?> matcher )
    {
        return Constraints.registerConstraint( argument, matcher );
    }

    public static void assertThat(boolean value)
    {
        if (!value)
        {
            try
            {
                AssertionFailure failure = new AssertionFailure("Assertion failed");
                /* remove ourself from the stack trace */
                StackTraceElement[] stackTrace = failure.getStackTrace();
                StackTraceElement[] newTrace = new StackTraceElement[stackTrace.length - 1];
                for (int i = 0 ; i < newTrace.length ; i++)
                    newTrace[i] = stackTrace[i + 1];
                failure.setStackTrace(newTrace);

                throw failure;
            }
            catch (Exception ex)
            {
                throw undeclared(ex);
            }
        }
    }

    /**
     * Asserts that <tt>actual</tt> matches the specified <tt>constraint</tt>, throwing an
     * <tt>{@link AssertionFailure}</tt> if it doesn't. The constraint is defined through static
     * method calls on the <tt>{@link Mocks}</tt> class which register the constraint and return a
     * special value of the type belonging to <tt>actual</tt>. For example, a simple assertion
     * that a string is either null or empty might be:
     *
     * <pre>
     * assertThat(s, or(null, &quot;&quot;));
     * </pre>
     *
     * The advantages of this assertion method over similar facilities are that it is type safe and
     * that failure messages are self describing. In the previous example, if <tt>s</tt> is not an
     * instance of String, the code will not compile. If <tt>s</tt> is the string "boff", the
     * assertion will fail with the message: <blockquote>org.mockfu.AssertionFailure:<br/>
     * Expected: String null or &quot;&quot;<br/> Encountered: &quot;boff&quot;</blockquote>
     *
     * @param <C>
     * @param <A>
     * @param actual the actual test result value
     * @param constraint a constraint key
     */
    public static <A, C extends A> void assertThat( A actual, C constraint )
    {
        try
        {
            List<Matcher<Object>> matchers = Constraints.getMatchers(true, constraint);
            if (matchers.isEmpty())
                throw new IllegalArgumentException(Constraints.toString(constraint) + " is not a constraint");
            if (!Constraints.current().isEmpty())
            {
                matchers = Constraints.getMatchers(false, actual);
                if (!matchers.isEmpty())
                    throw new IllegalArgumentException("Constraint " + matchers.get(matchers.size() - 1)
                            + " cannot be the first argument to assertThat()");
                StringBuilder s = new StringBuilder();
                for (Constraint each : Constraints.current())
                {
                    if (s.length() != 0)
                        s.append("; ");
                    s.append(each);
                }
                throw new IllegalStateException("Constraint defined in unexpected location: " + s);
            }

            Matcher<Object> matcher = matchers.get(0);
            if (!matcher.matches(actual))
            {
                AssertionFailure failure = new AssertionFailure(actual, constraint, matcher);

                /* remove ourself from the stack trace */
                StackTraceElement[] stackTrace = failure.getStackTrace();
                StackTraceElement[] newTrace = new StackTraceElement[stackTrace.length - 1];
                for (int i = 0 ; i < newTrace.length ; i++)
                    newTrace[i] = stackTrace[i + 1];
                failure.setStackTrace(newTrace);

                throw failure;
            }
        }
        finally
        {
            Constraints.current().clear();
        }
    }


    /**
     * Returns an instance of <tt>klas</tt> on which a method invocation specifies a method and
     * its arguments specify constraints. The return value of this invocation should be passed to
     * {@link #$(Object)} to get a {@link ConstrainedMethod} object. This allows for a succinct,
     * type-safe way of specifying a constrained method:
     *
     * <pre>
     * ConstrainedMethod m = $($(Calendar.class).get(Calendar.YEAR));
     * </pre>
     *
     * specifies the method "get" on the class <tt>Calendar</tt>, when called with the argument
     * <tt>Calendar.YEAR</tt>.
     *
     * @param <T>
     * @param klas the class declaring the method
     * @return a value that can return the specified constrained method
     */
    public static <T> T $( Class<T> klas )
    {
        return ProxyBuilder.of( klas, new MethodInterceptor() {
            public Object intercept( InvocationContext context ) throws Throwable
            {
                ConstrainedMethod method = new ConstrainedMethod( context.getMethod(), context.getArguments() );
                Constraints.setCurrentMethod( method );
                return null;
            }
        } ).create();
    }

    /**
     * Creates a {@link ConstrainedMethod} from the return value of an invocation on the object
     * returned by {@link #$(Class)}. This allows for a succinct, type-safe way of specifying a
     * constrained method:
     *
     * <pre>
     * ConstrainedMethod m = $( $( Calendar.class ).get( Calendar.YEAR ) );
     * </pre>
     *
     * specifies the method "get" on the class <tt>Calendar</tt>, when called with the argument
     * <tt>Calendar.YEAR</tt>.
     *
     * @param obj the return value from {@link #$(Class)}
     * @return a new constrained method
     */
    public static ConstrainedMethod $( Object obj )
    {
        ConstrainedMethod method = Constraints.currentMethod();
        if (method == null)
            throw new IllegalStateException("Method not specified");
        return method;
    }

    /**
     * Creates a {@link ConstrainedMethod} from a method with the specified class and name whose
     * signature matches the types of the argument constraints. This is equivalent to
     *
     * <pre>
     * new ConstrainedMethod( type, name, args );
     * </pre>.
     *
     * @param type the class in which the method is declared
     * @param name the name of the method
     * @param args the argument constraints
     * @return a new constrained method
     */
    public static ConstrainedMethod $( Class<?> type, String name, Object... args )
    {
        return new ConstrainedMethod( name, type, args );
    }


    public static <T> T nil()
    {
        return null;
    }


    static class EqMatcher extends Matcher<Object>
    {
        private static final String STRING_DIFF_START = System.getProperty( "string.diff.start", "[" );
        private static final String STRING_DIFF_END = System.getProperty( "string.diff.end", "]" );

        private final Object expected;
        private volatile Object actual;

        public EqMatcher(Object expected)
        {
            super("eq");
            this.expected = expected;
        }

        @Override
        public String toString()
        {
            return Constraints.toString(expected);
        }

        @Override
        public boolean matches(Object value)
        {
            actual = value;
            if (value == expected)
                return true;
            if (value == null || expected == null)
                return false;

            if (value.getClass().isArray())
            {
                if (value instanceof boolean[])
                    return Arrays.equals((boolean[])value, (boolean[])expected);
                if (value instanceof byte[])
                    return Arrays.equals((byte[])value, (byte[])expected);
                if (value instanceof char[])
                    return Arrays.equals((char[])value, (char[])expected);
                if (value instanceof double[])
                    return Arrays.equals((double[])value, (double[])expected);
                if (value instanceof float[])
                    return Arrays.equals((float[])value, (float[])expected);
                if (value instanceof int[])
                    return Arrays.equals((int[])value, (int[])expected);
                if (value instanceof long[])
                    return Arrays.equals((long[])value, (long[])expected);
                if (value instanceof Object[])
                    return Arrays.equals((Object[])value, (Object[])expected);
                if (value instanceof short[])
                    return Arrays.equals((short[])value, (short[])expected);
                throw new IllegalArgumentException("Unknown array type: " + value.getClass().getCanonicalName());
            }
            return value.equals(expected);
        }

        @Override
        public String description()
        {
            if (expected == null)
                return "null";
            String value = CharSequence.class.isInstance(expected)
                    ? getDiffString(actual, expected)
                    : Constraints.toString(expected);
            if (this.isDescriptionImplied())
                return value;
            return "equal to " + value;
        }

        private static String getDiffString(Object actual, Object expected)
        {
            if ("".equals(actual) && "".equals(expected))
                return "\"\"";
            if (actual == null || "".equals(actual))
                return "\"" + STRING_DIFF_START + expected + STRING_DIFF_END + "\"";

            String es = expected.toString();
            String as = actual.toString();
            /* get index in expected of first difference */
            int ai = 0, ei = 0, diffStart = -1, diffEnd = -1;
            while (ai < as.length() && ei < es.length())
            {
                if (as.charAt(ai) != es.charAt(ei))
                {
                    diffStart = ei;
                    break;
                }
                ei++;
                ai++;
            }
            if (diffStart == -1 && !as.equals(""))
                return "\"" + expected + "\"";
            /* get index in expected of last difference */
            ai = as.length() - 1;
            ei = es.length() - 1;
            while (true)
            {
                if (ei < diffStart)
                    diffEnd = diffStart;
                else if (as.charAt(ai) != es.charAt(ei))
                    diffEnd = ei + 1;
                else if (ai == 0)
                    diffEnd = ei + 1;
                else if (ei == 0)
                    diffEnd = 0;
                else
                {
                    ei--;
                    ai--;
                    continue;
                }
                break;
            }
            return "\"" + es.substring(0, diffStart) + STRING_DIFF_START + es.substring(diffStart, diffEnd)
                    + STRING_DIFF_END + es.substring(diffEnd) + "\"";
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + ((expected == null) ? 0 : expected.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (!super.equals(obj))
                return false;
            if (getClass() != obj.getClass())
                return false;
            final EqMatcher other = (EqMatcher)obj;
            if (expected == null)
            {
                if (other.expected != null)
                    return false;
            }
            else if (!expected.equals(other.expected))
                return false;
            return true;
        }


    }

    static class ArrayEqMatcher extends Matcher<Object>
    {
        private final List<Matcher<Object>> items;

        public ArrayEqMatcher( List<Matcher<Object>> nested )
        {
            super( "eq" );
            this.items = nested;
        }

        @Override
        public String description()
        {
            StringBuilder s = new StringBuilder( "of [" );
            for( Matcher<?> m : items )
            {
                if( s.length() != 4 )
                    s.append( ", " );
                m.setDescriptionImplied( true );
                s.append( m.description() );
            }
            return s.append( "]" ).toString();
        }

        @Override
        public boolean matches( Object value )
        {
            if( value == null )
                return false;
            if( !value.getClass().isArray() )
                return false;
            if( Array.getLength( value ) != items.size() )
                return false;
            boolean ret = true;
            for( int i = 0 ; i < items.size() ; i++ )
            {
                ret &= items.get( i ).matches( Array.get( value, i ) );
            }
            return ret;
        }

        @Override
        public String toString()
        {
            StringBuilder s = new StringBuilder( "{" );
            for( Matcher<?> m : items )
            {
                if( s.length() != 1 )
                    s.append( ", " );
                s.append( m );
            }
            return s.append( "}" ).toString();
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + ((items == null) ? 0 : items.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (!super.equals(obj))
                return false;
            if (getClass() != obj.getClass())
                return false;
            final ArrayEqMatcher other = (ArrayEqMatcher)obj;
            if (items == null)
            {
                if (other.items != null)
                    return false;
            }
            else if (!items.equals(other.items))
                return false;
            return true;
        }

    }

    static class NotMatcher extends Matcher<Object>
    {
        public NotMatcher( Object argument )
        {
            super( "not", argument );
        }

        @Override
        public boolean matches( Object value )
        {
            return !getNested().get( 0 ).matches( value );
        }

        @Override
        public String description()
        {
            return "not " + getNested().get( 0 ).description();
        }
    }

    /**
     * Creates a constraint which inverts result of another, nested constraint.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param constraint return value of the nested constraint
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C not( final C constraint )
    {
        return registerConstraint( constraint, new NotMatcher( constraint ) );
    }

    /**
     * Creates a constraint which matches only if all its nested constraints match.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param constraints return values of nested constraints
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C and( final C... constraints )
    {
        return registerConstraint( constraints[0], new Matcher<C>( "and", constraints ) {
            @Override
            public boolean matches( C value )
            {
                for( Matcher<Object> matcher : getNested() )
                {
                    if( !matcher.matches( value ) )
                        return false;
                }
                return true;
            }

            @Override
            public String description()
            {
                StringBuilder s = new StringBuilder();
                for( Matcher<Object> next : getNested() )
                {
                    if( s.length() != 0 )
                        s.append( next instanceof NotMatcher ? " but " : " and " );
                    s.append( next.description() );
                }
                return s.toString();
            }
        } );
    }

    /**
     * Creates a constraint which matches if any of its nested constraints match.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param constraints return values of nested constraints
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C or( final C... constraints )
    {
        return registerConstraint( constraints[0], new Matcher<C>( "or", constraints ) {
            @Override
            public boolean matches( C value )
            {
                for( Matcher<Object> matcher : getNested() )
                {
                    if( matcher.matches( value ) )
                        return true;
                }
                return false;
            }

            @Override
            public String description()
            {
                StringBuilder s = new StringBuilder();
                Class<?> lastType = null;
                for( Matcher<Object> next : getNested() )
                {
                    if( lastType == next.getClass() || this.isDescriptionImplied() )
                        next.setDescriptionImplied( true );
                    lastType = next.getClass();
                    if( s.length() != 0 )
                        s.append( " or " );
                    s.append( next.description() );
                }
                return s.toString();
            }
        } );
    }

    /**
     * Creates a constraint which matches when <tt>matchValue</tt> "equals" the actual value.
     * Equality for primitives (or boxed primitives) is checked using the == operator on the
     * primitive value, for arrays by calling the appropriate <tt>Arrays.equals()</tt> method, and
     * for other objects through <tt>Object.equals()</tt>. Null is equal only to null.
     * <p>
     * This constraint is the default when a literal object or primitive is passed to a method that
     * expects a constraint.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param matchValue the value matched against
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C eq( final C matchValue )
    {
        return registerConstraint( matchValue, new EqMatcher( matchValue ) );
    }

    /**
     * Creates a constraint which matches when <tt>matchValue</tt> is identical to the actual
     * value. Two values are identical if they reference the same object.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param matchValue the value (object reference) matched against
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C is( final C matchValue )
    {
        return registerConstraint( matchValue, new Matcher<C>( "is", matchValue ) {
            @Override
            public boolean matches( C value )
            {
                return matchValue == value;
            }

            @Override
            public String description()
            {
                String string = Constraints.toString( matchValue );
                if( matchValue instanceof Number )
                    return string;
                return string + " (@" + Integer.toHexString( System.identityHashCode( matchValue ) )
                        + ")";
            }

            @Override
            public ResultFormatter getResultFormatter()
            {
                return new ResultFormatter() {
                    public String format( Object value )
                    {
                        return "@" + Integer.toHexString( System.identityHashCode( value ) );
                    }
                };
            }
        } );
    }

    /**
     * Creates a constraint which matches when <tt>matchValue</tt> is identical to the actual
     * value. Two values are identical if they reference the same object.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param matchValue the value (object reference) matched against
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C same( final C matchValue )
    {
        return is( matchValue );
    }

    /**
     * Creates a constraint which matches anything.
     *
     * @param <C> type of the method parameter this constraint applies to
     * @param klass the type of the expected value
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <C> C any( Class<C> klass )
    {
        return registerConstraint( klass, new AnythingMatcher( "any" ) );
    }

    static final class AnythingMatcher extends Matcher<Object>
    {
        public AnythingMatcher( String name )
        {
            super( name );
        }

        @Override
        public boolean matches( Object value )
        {
            return true;
        }

        @Override
        public String description()
        {
            return "anything";
        }
    }


    public static <T extends Number> T lt( final T value )
    {
        return registerConstraint( value, new Matcher<T>( "lt", value ) {
            @Override
            public boolean matches( T actual )
            {
                return compare( actual, value ) < 0;
            }

            @Override
            public String description()
            {
                return "less than " + value;
            }
        } );
    }

    public static <T extends Number> T gt( final T value )
    {
        return registerConstraint( value, new Matcher<T>( "gt", value ) {
            @Override
            public boolean matches( T actual )
            {
                return compare( actual, value ) > 0;
            }

            @Override
            public String description()
            {
                return "greater than " + value;
            }
        } );
    }

    public static <T extends Number> T leq( final T value )
    {
        return registerConstraint( value, new Matcher<T>( "leq", value ) {
            @Override
            public boolean matches( T actual )
            {
                return compare( actual, value ) <= 0;
            }

            @Override
            public String description()
            {
                return "less than or equal to " + value;
            }
        } );
    }

    public static <T extends Number> T geq( final T value )
    {
        return registerConstraint( value, new Matcher<T>( "geq", value ) {
            @Override
            public boolean matches( T actual )
            {
                return compare( actual, value ) >= 0;
            }

            @Override
            public String description()
            {
                return "greater than or equal to " + value;
            }
        } );
    }

    public static <T extends Number> T between( final T bound1, final T bound2 )
    {
        return registerConstraint( bound1, new Matcher<T>( "between", bound1, bound2 ) {
            @Override
            public boolean matches( T value )
            {
                T min = bound1;
                T max = bound2;
                if( compare( min, max ) > 0 )
                {
                    min = bound2;
                    max = bound1;
                }
                return compare( min, value ) <= 0 && compare( max, value ) >= 0;
            }

            @Override
            public String description()
            {
                return "between " + bound1 + " and " + bound2;
            }
        } );
    }

    private static int compare( Number a, Number b )
    {
        if( a instanceof Byte || a instanceof Short || a instanceof Integer || a instanceof Long )
            return (int) (a.longValue() - b.longValue());
        else if( a instanceof Float )
        {
            float diff = a.floatValue() - b.floatValue();
            return diff == 0.0f ? 0 : diff > 0.0f ? 1 : -1;
        }
        else if( a instanceof Double )
        {
            double diff = a.doubleValue() - b.doubleValue();
            return diff == 0.0 ? 0 : diff > 0.0 ? 1 : -1;
        }
        else
            throw new IllegalArgumentException( "Unknown Number type: " + a.getClass() );
    }

    public static <C> C isA( final Class<C> klass )
    {
        return registerConstraint(klass, new Matcher<C>("isA", klass.getSimpleName())
        {
           @Override
           public boolean matches(C actual)
           {
               return klass.isInstance(actual);
           }

           @Override
           public String description()
           {
               return "of " + klass;
           }

           @Override
           public ResultFormatter getResultFormatter()
           {
               return new ResultFormatter()
               {
                   public String format(Object value)
                   {
                       return value.getClass().getSimpleName();
                   }
               };
           }
       });
    }

    /**
     * Registers a constraint which matches when the pattern <tt>regex</tt> matches the expected
     * CharSequence.
     *
     * @param regex pattern matched against
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <R extends CharSequence> R matches( final R regex )
    {
        return registerConstraint( regex, new Matcher<R>( "matches" ) {
            @Override
            public boolean matches( CharSequence actual )
            {
                if( actual == null )
                    return regex == null;
                return actual.toString().matches( regex.toString() );
            }

            @Override
            public String description()
            {
                return "matching /" + regex + "/";
            }

            @Override
            public String toString()
            {
                return "matches(/" + regex + "/)";
            }
        } );
    }

    /**
     * Registers a constraint which matches when the actual collection contains all of
     * <tt>values</tt>. <tt>values</tt> may also be nested constraints, allowing constructs
     * such as:
     *
     * <pre>
     * assertThat( list, contains( 1, or( 5, 7 ), not( 11 ) ) );
     * </pre>
     *
     * @param <N>
     * @param nested contained values or nest constraints
     * @return a key to be passed to other constraints, expectation parameters, or assertions, the
     *         position at which the constraint takes effect
     */
    public static <N> CollectionKey<N> contains( final N... nested )
    {
        return registerConstraint( CollectionKey.class,
                                   new Matcher<Collection<N>>( "contains", nested ) {
                                       @Override
                                    public boolean matches( Collection<N> actuals )
                                       {
                                           matching: for( Matcher<Object> matcher : getNested() )
                                           {
                                               for( Object actual : actuals )
                                               {
                                                   if( matcher.matches( actual ) )
                                                       continue matching;
                                               }
                                               return false;
                                           }
                                           return true;
                                       }

                                       @Override
                                    public String description()
                                       {
                                           StringBuilder s = new StringBuilder();
                                           for( Matcher<?> matcher : getNested() )
                                           {
                                               matcher.setDescriptionImplied( true );
                                               if( s.length() != 0 )
                                                   s.append( ", " );
                                               s.append( matcher.description() );
                                           }
                                           if( nested.length != 1 )
                                               s.insert( 0, "{" ).append( "}" );
                                           if( !isDescriptionImplied() )
                                               s.insert( 0, "containing " );
                                           return s.toString();
                                       }
                                   } );
    }

    public static <N> CollectionKey<N> equalsSet( final N... nested )
    {
        return registerConstraint( CollectionKey.class, new Matcher<Collection<N>>( "equalsSet",
                                                                                 nested ) {
            @Override
            public boolean matches( Collection<N> actuals )
            {
                List<Matcher<Object>> matchers = new ArrayList<Matcher<Object>>( getNested() );
                matching: for( Iterator<Matcher<Object>> i = matchers.iterator() ; i.hasNext() ; )
                {
                    Matcher<Object> matcher = i.next();
                    for( N actual : actuals )
                    {
                        if( matcher.matches( actual ) )
                        {
                            i.remove();
                            continue matching;
                        }
                    }
                    return false;
                }
                return matchers.isEmpty();
            }

            @Override
            public String description()
            {
                StringBuilder s = new StringBuilder();
                for( Matcher<?> matcher : getNested() )
                {
                    matcher.setDescriptionImplied( true );
                    if( s.length() != 0 )
                        s.append( ", " );
                    s.append( matcher.description() );
                }
                if( nested.length != 1 )
                    s.insert( 0, "{" ).append( "}" );
                if( !isDescriptionImplied() )
                    s.insert( 0, "set equal to " );
                return s.toString();
            }
        } );
    }

    /**
     * Assigns a stub value to an expectation. The stub value will be returned from a method invoked
     * on the mock matching the expectation.
     * <p>
     * This call stubs the last expectation defined in the current thread. The purpose of the
     * expectation parameter is simply to allow compile time type-checking of the stub value against
     * the return value of the expectation and is not actually used at runtime. For example, the
     * following returns "bar" when <tt>get("foo")</tt> is invoked on the mock of the <tt>Map</tt>
     * interface:
     *
     * <pre>
     * MockControl&lt;Map&gt; control = new MockControl( Map.class );
     * stub( (String) control.expect().get( &quot;foo&quot; ), &quot;bar&quot; );
     * </pre>
     *
     * The downcast to String is necessary in this instance because <tt>Map</tt> is not
     * genericized and the expectation return type must match the type of the stub value.
     *
     * @param <E> return type of the expectation
     * @param <S> stub value type, subtype of <E>
     * @param expectation expectation return value
     * @param stubValue stub return value
     */
    public static <E, S extends E> void stub( final E expectation, final S stubValue )
    {
        stub( expectation, new Stub<E>() {
            public E invoke( InvocationContext context ) throws Throwable
            {
                return stubValue;
            }
        } );
    }

    /**
     * Assigns a custom <tt>Stub</tt> implementation to an expectation which may be used instead
     * of a simple stub value to implement the side-effects of a method call on a mocked object.
     * <p>
     * This call stubs the last expectation defined in the current thread. The purpose of the
     * expectation parameter is simply to allow compile time type-checking of the stub value against
     * the return value of the expectation and is not actually used at runtime.
     *
     * @param <E> return type of the expectation
     * @param expectation expectation return value
     * @param stub a stub implementation
     */
    public static <E> void stub( E expectation, Stub<E> stub )
    {
        if (stub == null)
        {
            /* we probably meant this */
            stub = new Stub<E>() {
                public E invoke( InvocationContext context ) throws Throwable
                {
                    return null;
                }
            };
        }

        MockControl<?> control = MockControl.current();
        if( control == null )
            throw new IllegalStateException( "No expectation to stub!" );
        control.stubLastWith( stub );
    }

}
